package com.michaelbulava.TFS.tasks.connect;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.ui.config.ClientConnectionAdvisor;
import com.microsoft.tfs.core.TFSConfigurationServer;
import com.microsoft.tfs.core.TFSConnection;
import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.config.ConnectionAdvisor;
import com.microsoft.tfs.core.exceptions.TFSAccessException;
import com.microsoft.tfs.core.exceptions.TFSFederatedAuthException;
import com.microsoft.tfs.core.exceptions.TFSUnauthorizedException;
import com.microsoft.tfs.core.httpclient.Credentials;
import com.microsoft.tfs.core.httpclient.UsernamePasswordCredentials;
import com.microsoft.tfs.core.util.URIUtils;
import com.microsoft.tfs.core.ws.runtime.exceptions.TransportRequestHandlerCanceledException;
import com.microsoft.tfs.util.Check;

import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by mbulava on 6/16/2015.
 */
public class ConnectToConfigurationServerTask extends ConnectTask {

    public ConnectToConfigurationServerTask(Project project, URI serverURI) {
        super(project, serverURI);
    }

    public ConnectToConfigurationServerTask(Project project, URI serverURI, Credentials credentials) {
        super(project, serverURI, credentials);
    }

    @Override
    protected ConnectCommand getConnectCommand(URI serverURI, Credentials credentials) {
        return new ConnectToConfigurationServerCommand(serverURI, credentials);
    }

    @Override
    protected boolean runInBackground() {
        return false;
    }

    public static class ConnectToConfigurationServerCommand implements ConnectCommand{
        private static final Logger log = FileLogger.getInstance(ConnectToConfigurationServerCommand.class);
        private URI serverURI;
        private Credentials credentials;
        private TFSConnection connection;

        private Throwable exception;
        private boolean finishedOk = false;

        public ConnectToConfigurationServerCommand(URI serverURI, Credentials credentials)
        {
            Check.notNull(serverURI, "serverURI");
            Check.notNull(credentials, "credentials");

            this.serverURI = serverURI;
            this.credentials = credentials;

            //setCancellable(true);
            //TODO look at this might need or want to add a larger framework around it..
            //addExceptionHandler(new ConnectCommandExceptionHandler());
        }

        @Override
        public void execute(ProgressIndicator progress) {
            ConnectionAdvisor connectionAdvisor = new ClientConnectionAdvisor();
            String username = getUsername();
            String message;

            if (username != null) {
                String messageFormat = Messages.getString("ConnectToConfigurationServerCommand.ConnectingToServerAsFormat");
                message = MessageFormat.format(messageFormat, new Object[]{serverURI.toString(), username});
            } else {
                String messageFormat = Messages.getString("ConnectToConfigurationServerCommand.ConnectionToServerFormat");
                message = MessageFormat.format(messageFormat, new Object[] { serverURI.toString() });
            }
            log.info(message);
            progress.setText(message);
            progress.setIndeterminate(true);

            List<ConnectionURIAndType> connectionTypes = new ArrayList<ConnectionURIAndType>();
            connectionTypes.add(new ConnectionURIAndType(TFSConfigurationServer.class, this.serverURI));
            if ("/tfs".equals(this.serverURI.getPath())) {
                connectionTypes.add(new ConnectionURIAndType(TFSConfigurationServer.class, URIUtils.resolve(this.serverURI, "/")));
            }
            connectionTypes.add(new ConnectionURIAndType(TFSTeamProjectCollection.class, this.serverURI));
            if ("/tfs".equals(this.serverURI.getPath())) {
                connectionTypes.add(new ConnectionURIAndType(TFSTeamProjectCollection.class, URIUtils.resolve(this.serverURI, "/")));
            }
            this.connection = null;
            this.exception = null;
            while(connectionTypes.size() > 0){
                ConnectionURIAndType connectionData = (ConnectionURIAndType)connectionTypes.remove(0);
                try{
                    if (TFSConfigurationServer.class.equals(connectionData.getType())) {
                        this.connection = new TFSConfigurationServer(connectionData.getURI(), this.credentials, connectionAdvisor);
                    } else if (TFSTeamProjectCollection.class.equals(connectionData.getType())) {
                        this.connection = new TFSTeamProjectCollection(connectionData.getURI(), this.credentials, connectionAdvisor);
                    } else {
                        throw new RuntimeException("Unknown connection type");
                    }
                    this.connection.authenticate();
                    break; // Duh need to break out of the loop if I authenticate ok.
                } catch (Throwable e) {
                    if (this.connection != null)
                    {
                        this.credentials = this.connection.getCredentials();

                        this.connection.close();
                        this.connection = null;
                    }
                    if (((e instanceof TFSUnauthorizedException)) ||
                            ((e instanceof TFSAccessException)) ||
                            ((e instanceof TFSFederatedAuthException))) {
                        //throw e;
                        this.exception = e;
                        return;
                    }
                    if ((e instanceof TransportRequestHandlerCanceledException)) {
                        //return Status.CANCEL_STATUS;
                        this.exception = e;
                        return;
                    }
                    if (connectionTypes.size() == 0) {
                        //throw e;
                        this.exception = e;
                        return;
                    }
                    String messageFormat = "Server not found at {0}";

                    message = MessageFormat.format("Server not found at {0}", new Object[] { e.getLocalizedMessage() });
                    log.info(message);
                }
            }
            Check.notNull(this.connection, "connection");
            if (((this.connection instanceof TFSTeamProjectCollection)) && (((TFSTeamProjectCollection)this.connection).getConfigurationServer() != null)) {
                this.connection = ((TFSTeamProjectCollection)this.connection).getConfigurationServer();
            }
            finishedOk = true;
        }

        @Override
        public TFSConnection getConnection() {
            return this.connection;
        }

        @Override
        public boolean finishedOk() {
            return this.finishedOk;
        }

        @Override
        public Throwable getException() {
            return this.exception;
        }

        private String getUsername() {
            if ((this.credentials != null) && ((this.credentials instanceof UsernamePasswordCredentials)))
                return ((UsernamePasswordCredentials)this.credentials).getUsername();

            return null;
        }

    }

    private static final class ConnectionURIAndType
    {
        private final Class<? extends TFSConnection> type;
        private final URI uri;

        public ConnectionURIAndType(Class<? extends TFSConnection> type, URI uri)
        {
            Check.notNull(type, "type");
            Check.notNull(uri, "uri");

            this.type = type;
            this.uri = uri;
        }

        public Class<? extends TFSConnection> getType()
        {
            return this.type;
        }

        public URI getURI()
        {
            return this.uri;
        }
    }
}
